Skip to main content

Introduction to QA Automation

This lecture covers what automated testing is, the three interfaces we write tests through (functions, API, UI), and the types of tests you'll hear about in industry.

Repo: https://github.com/s1n7ax/lecture-intro-to-qa-automation-v2

old lecture notes


0. Getting started

  1. Open the repo above, then <> CodeCodespacesCreate codespace on main.
  2. Wait ~1–2 minutes — the Codespace auto-runs npm install && npx playwright install --with-deps chromium.
  3. Open a terminal (Ctrl+`) and run npm run test:unit. Green checkmarks mean you're ready.

1. What is test automation?

Testing checks that software does what it should. Instead of a human clicking through the app every release, you automate it: write code that exercises the app and asserts the result, then run it on demand in seconds.

Why automate?

  • Speed (hundreds of checks in seconds),
  • regression safety (catch what you broke today), and it
  • runs in CI on every push.

A test is always the same shape:

INITIAL STATE   set up the inputs / open the page
ACT call the function / click the button
ASSERT check the result is what you expected

2. The three interfaces (hands-on)

2a. Functions — Vitest

Test a pure function directly, no browser or network. The code under test (src/cart.js):

export function applyDiscount(amount, percent) {
if (percent < 0 || percent > 100) {
throw new Error(
`Discount percent must be between 0 and 100, got ${percent}`,
);
}
return amount - amount * (percent / 100);
}

The test (tests/unit/cart.test.js) — including the error case:

import { describe, it, expect } from "vitest";
import { applyDiscount } from "../../src/cart.js";

it("takes the right amount off", () => {
expect(applyDiscount(100, 20)).toBe(80);
});

it("rejects a discount above 100%", () => {
expect(() => applyDiscount(100, 150)).toThrow();
});

🖥️ Demo: npm run test:unit. Break src/cart.js (-+) and rerun to see a test go red.

Takeaway: unit tests are fast and precise — they pinpoint the exact function that's wrong.

2b. API — fetch + Vitest

Skip the UI and test the backend contract over HTTP, against the public Swagger Petstore. APIs describe themselves with an OpenAPI/Swagger spec, which also powers the Swagger UI ("Try it out").

it("GET /pet/findByStatus returns a list of available pets (200)", async () => {
const res = await fetch(
"https://petstore.swagger.io/v2/pet/findByStatus?status=available",
);
expect(res.status).toBe(200);
const pets = await res.json();
expect(Array.isArray(pets)).toBe(true);
});

🖥️ Demo: open https://petstore.swagger.io/, expand GET /pet/findByStatusTry it out → Execute. Then npm run test:api hits the same endpoint in code.

Takeaway: API tests are fast and don't depend on the UI — ideal for business logic and contracts.

2c. UI — Playwright

Drive a real browser like a user, using Playwright against the-internet.herokuapp.com. Playwright auto-waits for elements, which makes tests far less flaky.

import { test, expect } from "@playwright/test";

test("valid login lands on the secure area", async ({ page }) => {
await page.goto("/login");
await page.fill("#username", "tomsmith");
await page.fill("#password", "SuperSecretPassword!");
await page.click('button[type="submit"]');

await expect(page).toHaveURL(/.*secure/);
await expect(page.locator("#flash")).toContainText(
"You logged into a secure area!",
);
});

🖥️ Demo: npm run test:ui, then npm run test:ui:report for the trace and screenshots.

Takeaway: UI tests are the most realistic but slowest — use them for critical journeys, not everything.


3. Types of tests

These are the words you'll see in job ads and tickets — what you're testing and why, not the tool. Many are automated through the same three interfaces above.

Unit — Developer

One function or class in isolation.

Unit test example

Source: MS Calculator

Integration — Developer

Several units working together.

Integration test example

Source: Neovim

Smoke — QA

"Does the build even launch?"

Smoke test example

Source: VS Code

Performance — QA

Speed and responsiveness under normal use.

Performance test example

Source: Chrome Lighthouse (google.com)

Load — QA

Behaviour under heavy/concurrent traffic.

Load test example

Source: Anton Putra (YouTube)

Security — Security / QA

Vulnerabilities and misuse.

Security test example

Source: OWASP ZAP

End-to-End — QA

A whole user journey across the system.

E2E test example

Source: PeerTube

UI — QA

The interface behaves and looks right.

UI test example

Source: Meteor

API — QA

Endpoints honour their contract.

API test example

Source: json-server

Visual regression — QA

The UI didn't change pixels unexpectedly.

Visual regression test example

Source: Resemble.js

Functional, regression, and acceptance testing describe intent, not a tool — any of the tests above can serve those goals depending on what you're checking.


4. 🙌 Your turn — practical (~5 min)

You've seen a valid login test. Now write one for an invalid login (negative testing).

  1. Open tests/ui/practical.spec.js and follow the // TODO comments:
    • fill #username with wronguser, #password with wrongpass, click submit
    • assert #flash contains Your username is invalid!
  2. Run npm run test:ui. Stuck? The answer is in solutions/practical.solution.spec.js.

Bonus: also assert the page is still on /login (the user was not let in).


5. Recap

  • A test is always arrange → act → assert.
  • We automate through three interfaces, trading speed for realism:
    • Functions (Vitest) — instant, isolates logic.
    • API (fetch + Vitest) — fast, checks the backend contract.
    • UI (Playwright) — realistic, drives a real browser.
  • Test types (unit, integration, smoke, performance, security, …) describe what & why.

Go further